package licode.unisop.platform.application.executor;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import licode.unisop.platform.application.exception.PlatErrorCode;
import licode.unisop.platform.application.gateway.SiPlatProviderManager;
import licode.unisop.platform.client.api.SiPlatformQueryService;
import licode.unisop.platform.domain.PlatAuthItem;
import licode.unisop.platform.domain.PlatAuthManager;
import licode.unisop.provider.api.SiPlatformProvider;
import licode.unisop.provider.conf.SiAssetType;
import licode.unisop.provider.info.*;
import licode.unisop.provider.utils.SiAssetUtil;
import licode.unisop.provider.vo.*;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 平台逻辑功能实现
 *
 * @author licode
 */
@Primary
@Service
public class SiPlatformQueryExecutor implements SiPlatformQueryService {
    @Resource
    private PlatAuthManager platAuthManager;

    @Resource
    private SiPlatProviderManager siPlatProviderManager;

    @Override
    public SiPlatform lookupPlatform(SiPlatIdFind findInfo) {
        return findPlatformPlugin(findInfo).findPlatform(findInfo);
    }

    @Override
    public SiAssetEntry lookupAssetInfo(SiAssetId idFind) {
        return findPlatformPlugin(idFind.getOwnerId()).lookupAsset(idFind);
    }

    @Override
    public SiAssetBrief lookupAssetBrief(SiAssetLookupIn lookupIn) {
        return findPlatformPlugin(lookupIn.getOwnerId()).lookupAssetBrief(lookupIn);
    }

    @Override
    public Long getSubAssetCount(SiAssetLookupIn lookupIn) {
        return findPlatformPlugin(lookupIn.getOwnerId()).getSubAssetCount(lookupIn);
    }

    @Override
    public List<SiAssetKey> findAssetKeys(SiAssetId idFind) {
        return findPlatformPlugin(idFind.getOwnerId()).findAssetKeys(idFind);
    }

    @Override
    public SiAssetInfo getPlatformAssets(SiPlatIdFind plInfo) {
        PlatAuthItem platAuth = platAuthManager.findPlatAuthItem(SiPlatIdFind.builder()
                .platformId(plInfo.getPlatformId()).build());
        if (null != platAuth) {
            return platAuth.getAssetInfo();
        } else {
            return findPlatformPlugin(plInfo).getPlatAsset(plInfo);
        }
    }

    @Override
    public SiAssetBrief lookupAssetByCode(SiLookupAssetByCodeIn lookupIn) {
        return findPlatformPlugin(lookupIn.getPlatformId()).lookupAsseByCode(lookupIn);
    }

    @Override
    public SiAssetBrief lookupAuthAssetByCode(SiAssetCodeLookupIn req) {
        PlatAuthItem authItem;
        SiAssetBrief result = null;

        if (StrUtil.isBlank(req.getPlatformId()) ||
                StrUtil.isBlank(req.getAssetType()) ||
                StrUtil.isBlank(req.getCode())) {
            throw PlatErrorCode.PLATFORM_QUERY_ASSET_PARAM_ERROR.buildThrow();
        }

        authItem = platAuthManager.findPlatAuthItem(SiPlatIdFind.builder()
                .platformId(req.getPlatformId())
                .build());

        if (null != authItem && null != authItem.getAssetInfo() &&
                null != authItem.getAssetInfo().getAssets()) {
            for (Map.Entry<String, SiAsset> entry : authItem.getAssetInfo().getAssets().entrySet()) {
                if (entry.getValue().getType().equals(req.getAssetType()) &&
                        req.getCode().equals(entry.getValue().getCode())) {
                    result = BeanUtil.copyProperties(entry.getValue(), SiAssetBrief.class);
                    break;
                }
            }
        }

        return result;
    }

    @Override
    public List<SiAssetBrief> queryAssetsByCode(SiAssetCodeQueryIn req) {
        PlatAuthItem authItem;
        List<SiAssetBrief> result = new ArrayList<>();

        authItem = platAuthManager.findPlatAuthItem(SiPlatIdFind.builder()
                .platformId(req.getPlatformId())
                .build());
        if (null != authItem && null != authItem.getAssetInfo()) {
            authItem.getAssetInfo().getAssets().forEach((key, val) -> {
                if (val.getType().equals(req.getAssetType())) {
                    result.add(BeanUtil.copyProperties(val, SiAssetBrief.class));
                }
            });
        }
        return result;
    }

    @Override
    public SiAssetHold getUserAssets(SiHoldInfo holdInfo) {
        return findHoldAssets(holdInfo, true);
    }

    @Override
    public SiAssetHold getHoldAssets(SiHoldInfo holdInfo) {
        return findHoldAssets(holdInfo, false);
    }

    private SiAsset findAssetByCode(SiAssetInfo assetInfo, String code, String type) {
        for (Map.Entry<String, SiAsset> entry : assetInfo.getAssets().entrySet()) {
            if (type.equals(entry.getValue().getType()) &&
                    code.equals(entry.getValue().getCode())) {
                return entry.getValue();
            }
        }
        return null;
    }

    private SiAssetHold findHoldAssets(SiHoldInfo userInfo, boolean isUser) {
        SiAssetInfo assetInfo = null;
        List<SiAssetKey> assetKeys;

        SiPlatIdFind plInfo = SiPlatIdFind.builder()
                .tenantId(userInfo.getTenantId())
                .platformId(userInfo.getPlatformId())
                .build();
        PlatAuthItem item = platAuthManager.findPlatAuthItem(plInfo);

        if (null != item) {
            assetInfo = item.getAssetInfo();
        }
        if (null == assetInfo) {
            assetInfo = getPlatformAssets(plInfo);
        }

        if (null != assetInfo) {
            SiAssetId assetId = SiAssetId.builder()
                    .assetId(userInfo.getHoldId())
                    .assetType(userInfo.getHoldType())
                    .ownerId(userInfo.getPlatformId())
                    .build();
            // 如果holdId为空，获取所有
            if (StrUtil.isBlank(userInfo.getHoldId())) {
                assetKeys = assetInfo.getAssets().values().stream()
                        .map(key -> SiAssetKey.builder()
                                .id(key.getId()).type(key.getType()).build())
                        .collect(Collectors.toList());
            } else {
                assetKeys = findHoldKeysOfUser(assetInfo, assetId, userInfo.getClientId(), isUser);
            }

            return computeAssets(userInfo, assetInfo, assetKeys);
        }

        return null;
    }

    /**
     * 查找用户所拥有的资源ID（包括分组、权限、角色、菜单等）
     * @param assetInfo 平台资源信息
     * @param assetId 当前用户资源ID
     * @param clientCode 当前客户端
     * @param isUser 是否获取用户资源
     * @return 返回持有的资源信息
     */
    private List<SiAssetKey> findHoldKeysOfUser(SiAssetInfo assetInfo, SiAssetId assetId,
                                                String clientCode, boolean isUser) {
        List<SiAssetKey> assetKeys = findPlatformPlugin(assetId.getOwnerId()).findAssetKeys(assetId);

        /*
         * 如果是用户，填充分组资源信息
         */
        if (isUser) {
            List<SiAssetKey> grpAssetKeys = findGroupAssetForUser(assetId);
            if (null == assetKeys) {
                assetKeys = grpAssetKeys;
            } else if (null != grpAssetKeys) {
                assetKeys.addAll(grpAssetKeys);
            }
        }

        if (StrUtil.isNotBlank(clientCode)) {
            SiAsset clAsset = findAssetByCode(assetInfo, clientCode, SiAssetType.AT_CLIENT);
            if (null != clAsset) {
                if (null == assetKeys) {
                    assetKeys = new ArrayList<>();
                }
                assetKeys.add(SiAssetKey.builder()
                        .id(clAsset.getId()).type(clAsset.getType())
                        .build());
            }
        }

        return assetKeys;
    }

    private List<SiAssetKey> findGroupAssetForUser(SiAssetId assetId) {
        return findPlatformPlugin(assetId.getOwnerId()).findAssetKeys(SiAssetId
                .builder()
                .ownerId(assetId.getOwnerId())
                .targetId(assetId.getAssetId())
                .targetType(assetId.getAssetType())
                .build());
    }

    private SiPlatformProvider findPlatformPlugin(SiPlatIdFind info) {
        return siPlatProviderManager.findPlatProvider(info);
    }

    private SiPlatformProvider findPlatformPlugin(String ownerId) {
        return siPlatProviderManager.findPlatProvider(SiPlatIdFind
                .builder()
                .platformId(ownerId)
                .build());
    }

    private SiAssetHold computeAssets(SiHoldInfo holdInfo,
                                      SiAssetInfo assets,
                                      List<SiAssetKey> assetKeys) {
        Set<String> trees = new HashSet<>();
        SiAssetHold assInfo = new SiAssetHold();
        boolean filterEmpty = null == holdInfo.getTreeFilter() || holdInfo.getTreeFilter().isEmpty();

        assInfo.setHolderId(holdInfo.getHoldId());
        assInfo.setTenantId(assInfo.getTenantId());
        assInfo.setPlatformId(assets.getTargetId());

        Set<String> filters = filterEmpty ? null : new HashSet<>(holdInfo.getTreeFilter());

        if (null != assetKeys) {
            assetKeys.forEach(item -> {
                SiAsset asset = assets.getAssets().get(item.getId());
                if (null != asset) {
                    addAssetHold(assInfo, asset);
                    if (SiAssetUtil.isTree(asset)) {
                        trees.add(asset.getId());
                    }
                }
            });
        }

        /*
         * 处理树形资源，处理完这一步后，节点初步获取了所有节点
         */
        if (assInfo.hasIdAssets()) {
            handleTreeContain(holdInfo, assets, assInfo);
        }

        /*
         * 处理角色和分组和客户端包含的资源
         * 因为用户可能属于分组并且当前登录的是某个客户端，这里我们需要把分组和客户端包含的资源计算进来
         */
        handleGroupAndRoleOfAssets(trees, assets, assInfo);

        /*
         * 处理选择的树形资源节点（主要是菜单的结构）
         */
        if (!trees.isEmpty() && null != assets.getTrees()) {
            assets.getTrees().forEach(item -> {
                if (null == filters || filters.contains(item.getType())) {
                    SiAssetBO doAsset = filterAssetTree(assets, item, trees);
                    if (null != doAsset) {
                        assInfo.addTreeAsset(doAsset);
                    }
                }
            });
        }

        if (assInfo.getTrees() != null) {
            assInfo.getTrees().values().forEach(this::trimAndSortTree);
        }

        return assInfo;
    }

    private void addToTreeOfHold(Set<String> trees, SiAssetBO item) {
        if (SiAssetUtil.isTree(item)) {
            trees.add(item.getId());
        }
    }

    private void handleGroupAndRoleOfAssets(Set<String> trees, SiAssetInfo assets,  SiAssetHold assInfo) {
        Map<String, SiAssetBO> assetIds = null != assInfo.getIds() ?
                new HashMap<>(assInfo.getIds()) : new HashMap<>();
        assetIds.forEach((key, val) -> {
            if (SiAssetUtil.isHold(val)) {
                List<SiAssetKey> assetList = assets.getHolds().get(key);
                if (null != assetList) {
                    assetList.forEach(keyItem -> {
                        SiAsset asset = assets.getAssets().get(keyItem.getId());
                        SiAssetBO cloneAsset = cloneAsset(asset);
                        addAssetHold(assInfo, cloneAsset);
                        addToTreeOfHold(trees, cloneAsset);
                        if (SiAssetType.AT_ROLE.equals(asset.getType())) {
                            List<SiAssetKey> roleItems = assets.getHolds().get(cloneAsset.getId());
                            if (null != roleItems) {
                                roleItems.forEach(tmKey -> {
                                    SiAsset tmAsset = assets.getAssets().get(tmKey.getId());
                                    SiAssetBO cloneItem = cloneAsset(tmAsset);
                                    addAssetHold(assInfo, cloneItem);
                                    addToTreeOfHold(trees, cloneItem);
                                });
                            }
                        }
                    });
                }
            }
        });
    }

    private void trimAndSortTree(List<SiAssetBO> items) {
        SiAssetUtil.sortAssetBo(items);
    }

    private void handleTreeContain(SiHoldInfo holdInfo, SiAssetInfo assets, SiAssetHold assInfo) {
        List<SiAssetBO> doAssets = new ArrayList<>(assInfo.getIds().values());
        boolean filterEmpty = null == holdInfo.getDeepAssets() || holdInfo.getDeepAssets().isEmpty();
        Set<String> filters = filterEmpty ? null : new HashSet<>(holdInfo.getDeepAssets());

        for (SiAssetBO item : doAssets) {
            if (null == filters || filters.contains(item.getType())) {
                /*
                 * 判断拥有节点是否是树形节点，如果是， 则获取树根节点，然后把树的所有子节点加入
                 */
                SiAssetTKey findAsset = findAssetFromTree(assets.getTrees(), item.getId());
                if (null != findAsset) {
                    /*
                     * 把树的所有子节点加入
                     */
                    SiAsset doAsset = assets.getAssets().get(findAsset.getId());
                    handleContainItem(doAsset, assInfo);
                }
            }
        }
    }

    private SiAssetTKey findAssetFromTree(List<SiAssetTKey> assets, String id) {
        if (null != assets && !assets.isEmpty()) {
            for (SiAssetTKey item : assets) {
                if (item.getId().equals(id)) {
                    return item;
                }
                SiAssetTKey findAsset = findAssetFromTree(item.getChildren(), id);
                if (null != findAsset) {
                    return findAsset;
                }
            }
        }
        return null;
    }

    private void handleContainItem(SiAsset doAsset, SiAssetHold assInfo) {
        if (doAsset.hasChildren()) {
            doAsset.getChildren().forEach(item -> {
                addAssetHold(assInfo, item);
                handleContainItem(item, assInfo);
            });
        }
    }

    /**
     * 查找用户包含的树形节点信息（如果父节点没有包含但是子节点包含，父节点也会返回）
     * @param assets 平台资源信息
     * @param assetKey 当前根节点
     * @param trees 用户包含的资源节点
     * @return 返回规整后树形节点
     */
    private SiAssetBO filterAssetTree(SiAssetInfo assets, SiAssetTKey assetKey, Set<String> trees) {
        SiAssetBO newAsset = null;
        if (null == assetKey) {
            return newAsset;
        }
        SiAsset asset = assets.getAssets().get(assetKey.getId());

        if (assetKey.hasChildren()) {
            for (SiAssetTKey item : assetKey.getChildren()) {
                SiAssetBO vaAsset = filterAssetTree(assets, item, trees);
                if (null != vaAsset) {
                    if (null == newAsset) {
                        newAsset = cloneAsset(asset, "children");
                        newAsset.setChildren(new ArrayList<>());
                    }
                    newAsset.getChildren().add(vaAsset);
                }
            }

            if (null == newAsset && trees.contains(asset.getId())) {
                newAsset = cloneAsset(asset, "children");
            }
        } else if (trees.contains(asset.getId())) {
            newAsset = cloneAsset(asset, "children");
        }

        return newAsset;
    }

    private void addAssetHold(SiAssetHold assets, SiAssetBO assetBO) {
        if (null != assetBO.getProps()) {
            assetBO.getProps().removeProp("updateTime");
            assetBO.getProps().removeProp("createTime");
        }
        assets.addIdAsset(assetBO.getId(), assetBO);
        if (StrUtil.isNotBlank(assetBO.getCode())) {
            assets.addCodeAsset(assetBO.getType(), assetBO.getCode(), assetBO.getId());
        }
    }

    private void addAssetHold(SiAssetHold assets, SiAsset asset) {
        SiAssetBO assetBO = cloneAsset(asset);
        addAssetHold(assets, assetBO);
    }

    @Override
    public SiPageResult<SiPlatform> findPlatformPage(PlatPageInfo req) {
        return siPlatProviderManager.findPlatProvider(SiPlatIdFind
                .builder()
                .tenantId(req.getTenantId())
                .platformId("default")
                .build()).findPlatformPage(req);
    }

    @Override
    public SiPageResult<String> findAuthPlatformPage(AuthPlatPageInfo req) {
        return siPlatProviderManager.findPlatProvider(SiPlatIdFind
                .builder()
                .tenantId("")
                .platformId("default")
                .build()).findAuthPlatformPage(req);
    }

    @Override
    public List<SiAssetBrief> findAssetList(AssetListInfo req) {
        return siPlatProviderManager.findPlatProvider(SiPlatIdFind
                .builder()
                .tenantId(req.getTenantId())
                .platformId(req.getPlatformId())
                .build()).findAssetList(req);
    }

    @Override
    public SiPageResult<SiAssetRelation> findHoldRelAssetPage(HoldAssetPageInfo req) {
        return siPlatProviderManager.findPlatProvider(SiPlatIdFind
                .builder()
                .platformId(req.getOwnerId())
                .build()).findHoldRefAssetPage(req);
    }

    @Override
    public SiPageResult<SiAssetRelation> findAssetRelHoldPage(AssetHoldPageInfo req) {
        return siPlatProviderManager.findPlatProvider(SiPlatIdFind
                .builder()
                .platformId(req.getOwnerId())
                .build()).findAssetRefHoldPage(req);
    }

    @Override
    public SiPageResult<SiAssetBrief> findAssetPage(AssetPageInfo req) {
        if (null == req.getParentId()) {
            req.setParentId("");
        }

        if (null != req.getState() && req.getState() < 0) {
            req.setState(null);
        }

        return siPlatProviderManager.findPlatProvider(SiPlatIdFind
                .builder()
                .tenantId(req.getTenantId())
                .platformId(req.getPlatformId())
                .build()).findAssetPage(req);
    }

    @Override
    public SiPageResult<SiAssetBrief> findHoldAssetPage(HoldAssetPageInfo req) {
        return siPlatProviderManager.findPlatProvider(SiPlatIdFind
                .builder()
                .platformId(req.getOwnerId())
                .build()).findHoldAssetPage(req);
    }

    @Override
    public SiPageResult<SiAssetBrief> findAssetHoldPage(AssetHoldPageInfo req) {
        return siPlatProviderManager.findPlatProvider(SiPlatIdFind
                .builder()
                .platformId(req.getOwnerId())
                .build()).findAssetHoldPage(req);
    }

    private SiAssetBO cloneAsset(SiAsset asset, String... filterName) {
        return BeanUtil.copyProperties(asset, SiAssetBO.class, filterName);
    }
}
